/**
 * Copyright (c) 2011-2023, James Zhan 詹波 (jfinal@126.com).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.jfinal.template.stat;

import java.util.HashMap;
import java.util.Map;

/**
 * Scope
 * 1：顶层 scope.parent 为 null
 * 2：scope.set(...) 自内向外查找赋值
 * 3：scope.get(...) 自内向外查找获取
 */
@SuppressWarnings({"rawtypes", "unchecked"})
public class Scope {

    private final Scope parent;
    private final Ctrl ctrl;
    private Map data;
    private Map<String, Object> sharedObjectMap;

    /**
     * 构建顶层 Scope， parent 为 null 是顶层 Scope 的标志
     * @param data 用于在模板中使用的数据，data 支持 null 值
     * @param sharedObjectMap 共享对象
     */
    public Scope(Map data, Map<String, Object> sharedObjectMap) {
        this.parent = null;
        this.ctrl = new Ctrl();
        this.data = data;
        this.sharedObjectMap = sharedObjectMap;
    }

    /**
     * 构建 AST 执行过程中作用域栈
     */
    public Scope(Scope parent) {
        if (parent == null) {
            throw new IllegalArgumentException("parent can not be null.");
        }
        this.parent = parent;
        this.ctrl = parent.ctrl;
        this.data = null;
        this.sharedObjectMap = parent.sharedObjectMap;
    }

    public Ctrl getCtrl() {
        return ctrl;
    }

    /**
     * 设置变量
     * 自内向外在作用域栈中查找变量，如果找到则改写变量值，否则将变量存放到顶层 Scope
     */
    public void set(Object key, Object value) {
        for (Scope cur=this; true; cur=cur.parent) {
            // HashMap 允许有 null 值 value，必须要做 containsKey 判断
            if (cur.data != null && cur.data.containsKey(key)) {
                cur.data.put(key, value);
                return ;
            }

            if (cur.parent == null) {
                if (cur.data == null) {			// 支持顶层 data 为 null 值
                    cur.data = new HashMap();
                }

                cur.data.put(key, value);
                return ;
            }
        }
    }

    /**
     * 获取变量
     * 自内向外在作用域栈中查找变量，返回最先找到的变量
     */
    public Object get(Object key) {
        Scope cur = this;
        do {
//			if (cur.data != null && cur.data.containsKey(key)) {
//				return cur.data.get(key);
//			}

            if (cur.data != null) {
                Object ret = cur.data.get(key);
                if (ret != null) {
                    return ret;
                }

                if (cur.data.containsKey(key)) {
                    return null;
                }
            }

            cur = cur.parent;
        } while (cur != null);

        // return null;
        return sharedObjectMap != null ? sharedObjectMap.get(key) : null;
    }

    /**
     * 移除变量
     * 自内向外在作用域栈中查找变量，移除最先找到的变量
     */
    public void remove(Object key) {
        for (Scope cur=this; cur!=null; cur=cur.parent) {
            if (cur.data != null && cur.data.containsKey(key)) {
                cur.data.remove(key);
                return ;
            }
        }
    }

    /**
     * 设置局部变量
     */
    public void setLocal(Object key, Object value) {
        if (data == null) {
            data = new HashMap();
        }
        data.put(key, value);
    }

    /**
     * 获取局部变量
     */
    public Object getLocal(Object key) {
        return data != null ? data.get(key) : null;
    }

    /**
     * 移除局部变量
     */
    public void removeLocal(Object key) {
        if (data != null) {
            data.remove(key);
        }
    }

    /**
     * 设置全局变量
     * 全局作用域是指本次请求的整个 template
     */
    public void setGlobal(Object key, Object value) {
        for (Scope cur=this; true; cur=cur.parent) {
            if (cur.parent == null) {
                if (cur.data == null) {
                    cur.data = new HashMap();
                }

                cur.data.put(key, value);
                return ;
            }
        }
    }

    /**
     * 获取全局变量
     * 全局作用域是指本次请求的整个 template
     */
    public Object getGlobal(Object key) {
        for (Scope cur=this; true; cur=cur.parent) {
            if (cur.parent == null) {
                return cur.data != null ? cur.data.get(key) : null;
            }
        }
    }

    /**
     * 移除全局变量
     * 全局作用域是指本次请求的整个 template
     */
    public void removeGlobal(Object key) {
        for (Scope cur=this; true; cur=cur.parent) {
            if (cur.parent == null) {
                if (cur.data != null) {
                    cur.data.remove(key);
                }

                return ;
            }
        }
    }

    /**
     * 自内向外在作用域栈中查找变量，获取变量所在的 Map，主要用于 IncDec
     */
    public Map getMapOfValue(Object key) {
        for (Scope cur=this; cur!=null; cur=cur.parent) {
            if (cur.data != null && cur.data.containsKey(key)) {
                return cur.data;
            }
        }
        return null;
    }

    /**
     * 获取本层作用域 data，可能为 null 值
     */
    public Map getData() {
        return data;
    }

    /**
     * 设置/替换本层作用域 data，通常用于在扩展指令中使用现成可用的 Map 来存放数据，
     * 从而避免 Scope 内部创建 data，节省时空
     *
     * 注意：本方法会替换掉已经存在的 data 对象
     */
    public void setData(Map data) {
        this.data = data;
    }

    /**
     * 获取顶层作用域 data，可能为 null 值
     */
    public Map getRootData() {
        for (Scope cur=this; true; cur=cur.parent) {
            if (cur.parent == null) {
                return cur.data;
            }
        }
    }

    /**
     * 设置/替换顶层作用域 data，可以在扩展指令之中通过此方法切换掉顶层作用域
     * 实现作用域完全隔离的功能
     *
     * 注意：本方法会替换掉顶层已经存在的 data 对象
     */
    public void setRootData(Map data) {
        for (Scope cur=this; true; cur=cur.parent) {
            if (cur.parent == null) {
                cur.data = data;
                return ;
            }
        }
    }

    /**
     * 自内向外在作用域栈中查找变量是否存在
     */
    public boolean exists(Object key) {
        for (Scope cur=this; cur!=null; cur=cur.parent) {
            if (cur.data != null && cur.data.containsKey(key)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 获取共享对象
     */
    public Object getSharedObject(String key) {
        return sharedObjectMap != null ? sharedObjectMap.get(key) : null;
    }
}



